package com.bailun91.auth.jwt;

import com.bailun91.auth.entity.JsonData;
import com.bailun91.auth.service.UserService;
import com.bailun91.auth.util.BeanUtil;
import com.bailun91.auth.util.Constants;
import com.sun.org.apache.bcel.internal.generic.IFGE;
import org.apache.shiro.authc.Account;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class JwtFilter extends AccessControlFilter {

    /**
     * 对跨域提供支持
     */
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("preHandle 执行了");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }

        return super.preHandle(request, response);
    }

    /**
     * TokenExpiredException, InvalidClaimException AlgorithmMismatchException JWTVerificationException
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        System.out.println("ShiroJwtFilter 的 isAccessAllowed 执行了");

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;


        if (isLoginAttempt(request, response)) {//请求头是否带有token,并且token可用
            try {
                executeLogin(request, response);
                //登录成功，token校验通过,刷新refreshToken的过期时间
                updateRefreshToken(request);
                return true;

            } catch (Exception e) {//1.token 非法 2.token 失效 3.其他原因
                String exceptionMessage = e.getMessage();
                String exceptionName = e.getClass().getName();
                if (exceptionMessage.contains("TokenExpiredException")) {//accessToken过期
                    if (checkRefreshTokenIfNeed(request)) {//是否需要发放accessToken
                        updateAccessToken(request, response);//重新发放accessToken，更新refreshToken
                        return true;
                    }
                }
            }
        } else {//请求没有带token,判断是否是登录请求，是，放行。不是，拦截返回false
            String loginUrl = httpServletRequest.getRequestURI();
            if (loginUrl.equals("/login")) {
                return true;
            }
        }
        return false;
    }

    /**
     * 如果这个Filter在之前isAccessAllowed()方法中返回false,则会进入这个方法。我们这里直接返回错误的response,说明登录认证失败了
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("ShiroJwtFilter 的 onAccessDenied 执行了");
        response401(request, response);
        return false;
    }

    /**
     * 检查是否需要重新申请AccessToken给用户
     * 当用户携带的AccessToken过期时，查看redis缓存中的refreshToken是否有过期
     *
     * @param request
     */
    protected Boolean checkRefreshTokenIfNeed(ServletRequest request) {
        RedisTemplate<String, Object> redisTemplate = BeanUtil.getBean(Constants.FASTJSONREDISTEMPLATE);
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader(Constants.ACCESS_TOKEN_NAME);
        String userName = JwtUtil.getUserNameFromToken(token);
        String refreshToken = (String) redisTemplate.opsForValue().get(userName);
        return null != refreshToken;
    }

    /**
     * accessToken未过期，只刷新RefreshToken
     *
     * @param request
     * @return
     */
    protected void updateRefreshToken(ServletRequest request) {
        RedisTemplate<String, Object> redisTemplate = BeanUtil.getBean(Constants.FASTJSONREDISTEMPLATE);
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader(Constants.ACCESS_TOKEN_NAME);
        String userName = JwtUtil.getUserNameFromToken(token);
        redisTemplate.opsForValue().set(Constants.REFRESHTOKENPRE_CRM + userName, token, Constants.REFRESH_TOKEN_EXP_TIME, TimeUnit.SECONDS);
    }

    /**
     * accessToken过期，refreshToken未过期，刷新AccessToken，同时刷新refreshToken
     *
     * @param request
     * @param response
     * @return
     */
    protected void updateAccessToken(ServletRequest request, ServletResponse response) {

        RedisTemplate<String, Object> redisTemplate = BeanUtil.getBean(Constants.FASTJSONREDISTEMPLATE);
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse rep = (HttpServletResponse) response;
        String token = req.getHeader(Constants.ACCESS_TOKEN_NAME);
        String userName = JwtUtil.getUserNameFromToken(token);
        String newToken = JwtUtil.createJWT(userName, req);
        rep.setHeader(Constants.ACCESS_TOKEN_NAME, newToken);
        rep.setHeader("Access-Control-Expose-Headers", Constants.ACCESS_TOKEN_NAME);
        redisTemplate.opsForValue().set(Constants.REFRESHTOKENPRE_CRM + userName, newToken, Constants.REFRESH_TOKEN_EXP_TIME, TimeUnit.SECONDS);
    }

    /**
     * 判断用户是否想要登入。
     * 检测header里面是否包含Authorization字段,并且token是可用的
     */
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        System.out.println("ShiroJwtFilter的 isLoginAttempt 方法执行了");

        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader(Constants.ACCESS_TOKEN_NAME);
        return (token != null && token.length() != 0);
    }

    /**
     * 执行登录
     */
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {

        System.out.println("ShiroJwtFilter的 executeLogin 方法执行了");

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authorization = httpServletRequest.getHeader(Constants.ACCESS_TOKEN_NAME);
        JwtToken token = new JwtToken(authorization);

        /**
         * 这里我提前把token的校验做了，到JwtRealm只是简单的校验
         */
        Boolean success = JwtUtil.VerifyTokenDevInfo(authorization, httpServletRequest);
        // 提交给realm进行登入，如果错误他会抛出异常并
        // 被捕获
        if (success) {
            getSubject(request, response).login(token);
        }
        // 如果没有抛出异常则代表登入成功，返回true
        return true;
    }

    /**
     * 将非法请求跳转到 /401
     */
    private void response401(ServletRequest req, ServletResponse resp) {

        System.out.println("执行了 JwtFilter 的 response401 无权限访问");
        HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        try {
            ((HttpServletResponse) resp).sendRedirect("/401");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }


}
